/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package shaman.storage.impl.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 *
 * @author dmitriy
 */
public class StreamUtil {

    public static void writeUtf8Char(OutputStream stream, int codePoint) throws IOException {
        if (codePoint<0)
            throw new IOException("Illegal UTF-8 codepoint");
        else if (codePoint<0x80)
            stream.write(codePoint);
        else if (codePoint<0x800)
        {
            stream.write(0xC0 | codePoint>>6);
            stream.write(0x80 | codePoint&0x3F);
        }
        else if (codePoint<0x10000)
        {
            stream.write(0xE0 | codePoint>>12);
            stream.write(0x80 | (codePoint>>6)&0x3F);
            stream.write(0x80 | codePoint&0x3F);
        }
        else if (codePoint<0x200000)
        {
            stream.write(0xF0 | codePoint>>18);
            stream.write(0x80 | (codePoint>>12)&0x3F);
            stream.write(0x80 | (codePoint>>6)&0x3F);
            stream.write(0x80 | codePoint&0x3F);
        }
        else if (codePoint<0x4000000)
        {
            stream.write(0xF8 | codePoint>>24);
            stream.write(0x80 | (codePoint>>18)&0x3F);
            stream.write(0x80 | (codePoint>>12)&0x3F);
            stream.write(0x80 | (codePoint>>6)&0x3F);
            stream.write(0x80 | codePoint&0x3F);
        }
        else //if (codePoint<0x80000000)
        {
            stream.write(0xFC | codePoint>>30);
            stream.write(0x80 | (codePoint>>24)&0x3F);
            stream.write(0x80 | (codePoint>>18)&0x3F);
            stream.write(0x80 | (codePoint>>12)&0x3F);
            stream.write(0x80 | (codePoint>>6)&0x3F);
            stream.write(0x80 | codePoint&0x3F);
        }
    }

    public static int readUtf8Char(InputStream stream) throws IOException {
        int codePoint=0;
        int minCodePoint=0;
        int steps;
        int startByte=stream.read();
        if (startByte<0)
            return -1;
        else if (startByte<0x80)
            return startByte;
        else if (startByte<0xC0)
            throw new IOException("Malformed UTF-8 codepoint");
        else if (startByte<0xE0)
        {
            steps=2;
            minCodePoint=0x80;
        }
        else if (startByte<0xF0)
        {
            steps=3;
            minCodePoint=0x800;
        }
        else if (startByte<0xF8)
        {
            steps=4;
            minCodePoint=0x10000;
        }
        else if (startByte<0xFC)
        {
            steps=5;
            minCodePoint=0x200000;
        }
        else
        {
            steps=6;
            minCodePoint=0x4000000;
        }
        codePoint=startByte&(0xFF>>steps);
        int i;
        for (i=1; i<steps; i++)
        {
            int nextByte=stream.read();
            if (nextByte<0)
                throw new IOException("Unexpected EOF in UTF-8 codepoint");
            else if (nextByte<0x80)
                throw new IOException("Incomplete UTF-8 codepoint");
            else if (nextByte<0xC0)
            {
                codePoint<<=6;
                codePoint|=(nextByte&0x3F);
            }
            else
                throw new IOException("Incomplete UTF-8 codepoint");
        }
        if (codePoint<minCodePoint)
            throw new IOException("Incorrectly represented UTF-8 codepoint");
        return codePoint;
    }

    public static void writeString(OutputStream stream, String str) throws IOException
    {
        int len=str.length();
        writeUtf8Char(stream, len);
        int i;
        for (i=0; i<len; i++)
        {
            writeUtf8Char(stream, str.charAt(i));
        }
    }

    public static String readString(InputStream stream) throws IOException
    {
        int len=readUtf8Char(stream);
        if (len<0) return null;
        StringBuilder bld=new StringBuilder(len);
        bld.setLength(len);
        int i;
        for (i=0; i<len; i++)
        {
            int cp=readUtf8Char(stream);
            if (cp<0)
                throw new IOException("Unexpected EOF reading string");
            bld.setCharAt(i, (char) cp);
        }
        return bld.toString();
    }

    public static void writeBlob(OutputStream stream, byte[] blob) throws IOException
    {
        int len=blob.length;
        writeUtf8Char(stream, len);
        stream.write(blob);
    }

    public static byte[] readBlob(InputStream stream) throws IOException
    {
        int len=readUtf8Char(stream);
        if (len<0) return null;
        byte[] data=new byte[len];
        int dataPos=0;
        while (dataPos<len)
        {
            int readLen=stream.read(data, dataPos, len-dataPos);
            if (readLen<0)
                throw new IOException("Unexpected EOF reading blob");
            dataPos+=readLen;
        }
        return data;
    }
}
